// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"bytes"
	"fmt"
	"html"
	"io"
	"text/template"
	"text/template/parse"
)

// escapeTemplate重写命名模板，该模板必须是与t关联的
// 以保证任何命名
// 模板的输出都被正确转义。如果未返回任何错误，则已修改命名模板
// 。否则，命名模板将被呈现为
// 不可用。
func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
	c, _ := tmpl.esc.escapeTree(context{}, node, name, 0)
	var err error
	if c.err != nil {
		err, c.err.Name = c.err, name
	} else if c.state != stateText {
		err = &Error{ErrEndContext, nil, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
	}
	if err != nil {
		// 防止执行不安全的模板。
		if t := tmpl.set[name]; t != nil {
			t.escapeErr = err
			t.text.Tree = nil
			t.Tree = nil
		}
		return err
	}
	tmpl.esc.commit()
	if t := tmpl.set[name]; t != nil {
		t.escapeErr = escapeOK
		t.Tree = t.text.Tree
	}
	return nil
}

// evalArgs将参数列表格式化为字符串。它相当于
// fmt.Sprint（args…），只是它遵从所有指针。
func evalArgs(args ...interface{}) string {
	// 针对单个字符串参数的简单常见情况进行优化。
	if len(args) == 1 {
		if s, ok := args[0].(string); ok {
			return s
		}
	}
	for i, arg := range args {
		args[i] = indirectToStringerOrError(arg)
	}
	return fmt.Sprint(args...)
}

// funcMap将命令名映射到使其输入安全的函数。
var funcMap = template.FuncMap{
	"_html_template_attrescaper":     attrEscaper,
	"_html_template_commentescaper":  commentEscaper,
	"_html_template_cssescaper":      cssEscaper,
	"_html_template_cssvaluefilter":  cssValueFilter,
	"_html_template_htmlnamefilter":  htmlNameFilter,
	"_html_template_htmlescaper":     htmlEscaper,
	"_html_template_jsregexpescaper": jsRegexpEscaper,
	"_html_template_jsstrescaper":    jsStrEscaper,
	"_html_template_jsvalescaper":    jsValEscaper,
	"_html_template_nospaceescaper":  htmlNospaceEscaper,
	"_html_template_rcdataescaper":   rcdataEscaper,
	"_html_template_srcsetescaper":   srcsetFilterAndEscaper,
	"_html_template_urlescaper":      urlEscaper,
	"_html_template_urlfilter":       urlFilter,
	"_html_template_urlnormalizer":   urlNormalizer,
	"_eval_args_":                    evalArgs,
}

// escaper收集有关模板的类型推断以及使
// 模板注入安全所需的更改。
type escaper struct {
	// ns是与此转义器关联的命名空间。
	ns *nameSpace
	// output[templateName]是一个模板名的输出上下文，该模板名已被修改为包含其输入上下文。
	output map[string]context
	// 派生的[c.mangle（name）]映射到从模板派生的模板
	// 开始上下文c的命名名称templateName。
	derived map[string]*template.Template
	// 调用的[templateName]是一组被称为损坏的模板名称。
	called map[string]bool
	// xxxNodeEdits是提交期间应用的累积编辑。
	// 如果模板集
	// 在不同转义上下文中执行给定模板，则不会立即应用此类编辑。
	actionNodeEdits   map[*parse.ActionNode][]string
	templateNodeEdits map[*parse.TemplateNode]string
	textNodeEdits     map[*parse.TextNode][]byte
}

// makeEscaper为给定集创建一个空转义器。
func makeEscaper(n *nameSpace) escaper {
	return escaper{
		n,
		map[string]context{},
		map[string]*template.Template{},
		map[string]bool{},
		map[*parse.ActionNode][]string{},
		map[*parse.TemplateNode]string{},
		map[*parse.TextNode][]byte{},
	}
}

// filterFailsafe是一个无害的词，由消毒剂函数代替不安全值发出。它不是任何编程语言中的关键词，
// 不包含特殊字符，也不是空的，当它出现在输出
// 中时，它非常清晰，开发者可以通过搜索引擎找到问题的根源。
const filterFailsafe = "ZgotmplZ"

// 转义将转义模板节点。
func (e *escaper) escape(c context, n parse.Node) context {
	switch n := n.(type) {
	case *parse.ActionNode:
		return e.escapeAction(c, n)
	case *parse.CommentNode:
		return c
	case *parse.IfNode:
		return e.escapeBranch(c, &n.BranchNode, "if")
	case *parse.ListNode:
		return e.escapeList(c, n)
	case *parse.RangeNode:
		return e.escapeBranch(c, &n.BranchNode, "range")
	case *parse.TemplateNode:
		return e.escapeTemplate(c, n)
	case *parse.TextNode:
		return e.escapeText(c, n)
	case *parse.WithNode:
		return e.escapeBranch(c, &n.BranchNode, "with")
	}
	panic("escaping " + n.String() + " is unimplemented")
}

// escapeAction转义操作模板节点。
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
	if len(n.Pipe.Decl) != 0 {
		// 局部变量赋值，而不是插值。
		return c
	}
	c = nudge(c)
	// 检查管道中是否不允许使用预定义的转义器。
	for pos, idNode := range n.Pipe.Cmds {
		node, ok := idNode.Args[0].(*parse.IdentifierNode)
		if !ok {
			// 在
			// 链或字段节点中永远找不到作为标识符的预定义转义符“esc”，因为：
			// /-“esc.x…”无效，因为预定义转义符返回字符串，而
			// 字符串没有方法、键或字段。
			// /-“…esc”无效，因为预定义的转义符是全局函数，而不是任何类型的方法或字段。
			// 因此，可以安全地忽略这两种节点类型。
			continue
		}
		ident := node.Ident
		if _, ok := predefinedEscapers[ident]; ok {
			if pos < len(n.Pipe.Cmds)-1 ||
				c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
				return context{
					state: stateError,
					err:   errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
				}
			}
		}
	}
	s := make([]string, 0, 3)
	switch c.state {
	case stateError:
		return c
	case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
		switch c.urlPart {
		case urlPartNone:
			s = append(s, "_html_template_urlfilter")
			fallthrough
		case urlPartPreQuery:
			switch c.state {
			case stateCSSDqStr, stateCSSSqStr:
				s = append(s, "_html_template_cssescaper")
			default:
				s = append(s, "_html_template_urlnormalizer")
			}
		case urlPartQueryOrFrag:
			s = append(s, "_html_template_urlescaper")
		case urlPartUnknown:
			return context{
				state: stateError,
				err:   errorf(ErrAmbigContext, n, n.Line, "%s appears in an ambiguous context within a URL", n),
			}
		default:
			panic(c.urlPart.String())
		}
	case stateJS:
		s = append(s, "_html_template_jsvalescaper")
		// 值后的斜杠启动div运算符。
		c.jsCtx = jsCtxDivOp
	case stateJSDqStr, stateJSSqStr:
		s = append(s, "_html_template_jsstrescaper")
	case stateJSRegexp:
		s = append(s, "_html_template_jsregexpescaper")
	case stateCSS:
		s = append(s, "_html_template_cssvaluefilter")
	case stateText:
		s = append(s, "_html_template_htmlescaper")
	case stateRCDATA:
		s = append(s, "_html_template_rcdataescaper")
	case stateAttr:
		// 在delim检查中处理如下。
	case stateAttrName, stateTag:
		c.state = stateAttrName
		s = append(s, "_html_template_htmlnamefilter")
	case stateSrcset:
		s = append(s, "_html_template_srcsetescaper")
	default:
		if isComment(c.state) {
			s = append(s, "_html_template_commentescaper")
		} else {
			panic("unexpected state " + c.state.String())
		}
	}
	switch c.delim {
	case delimNone:
		// 原始文本内容不需要额外转义。
	case delimSpaceOrTagEnd:
		s = append(s, "_html_template_nospaceescaper")
	default:
		s = append(s, "_html_template_attrescaper")
	}
	e.editActionNode(n, s)
	return c
}

// ensurePipelineContains确保管道以带有
// 标识符的命令结束。如果管道以预定义的转义符
// （即“html”或“urlquery”）结束，请将其与s中的标识符合并。
func ensurePipelineContains(p *parse.PipeNode, s []string) {
	if len(s) == 0 {
		// 如果我们没有要插入的转义符，请不要重写管道。
		return
	}
	// 前提条件：p.Cmds最多包含一个预定义的转义符，并且
	// 转义符将出现在p.Cmds[len（p.Cmds）-1]中。这个前提条件是
	// 由于escapeAction中的检查，所以始终为true。
	pipelineLen := len(p.Cmds)
	if pipelineLen > 0 {
		lastCmd := p.Cmds[pipelineLen-1]
		if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok {
			if esc := idNode.Ident; predefinedEscapers[esc] {
				// 管道以预定义的转义符结束。
				if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 {
					// 特殊情况：管道的形式为{esc arg1 arg2…argN}，
					// 其中esc是预定义的转义符，arg1…argN是其参数。
					// 将其转换为等价形式
					// /{{{{u eval_args_arg1arg2…argN | esc}}，以便esc可以轻松地与s中的转义符合并。
					lastCmd.Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd.Args[0].Position())
					p.Cmds = appendCmd(p.Cmds, newIdentCmd(esc, p.Position()))
					pipelineLen++
				}
				// 如果要插入的s中的任何命令与预定义的转义符
				// 等效，请改用预定义的转义符。
				dup := false
				for i, escaper := range s {
					if escFnsEq(esc, escaper) {
						s[i] = idNode.Ident
						dup = true
					}
				}
				if dup {
					// 预定义的转义符将与
					// 转义符一起插入s中，因此不要将其复制到重写的管道中。
					pipelineLen--
				}
			}
		}
	}
	// 重写管道，在管道末尾的s中创建转义符。
	newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
	insertedIdents := make(map[string]bool)
	for i := 0; i < pipelineLen; i++ {
		cmd := p.Cmds[i]
		newCmds[i] = cmd
		if idNode, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
			insertedIdents[normalizeEscFn(idNode.Ident)] = true
		}
	}
	for _, name := range s {
		if !insertedIdents[normalizeEscFn(name)] {
			// 当两个模板通过使用
			// AddParseTree共享一个底层解析树，并且一个模板在另一个模板之后执行时，此检查
			// 确保在
			// 上已插入管道的转义符不会再次插入第一个转义过程。
			newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
		}
	}
	p.Cmds = newCmds
}

// 预定义的转义符包含与某些上下文转义符等效的模板预定义转义符。与逃犯保持同步。
var predefinedEscapers = map[string]bool{
	"html":     true,
	"urlquery": true,
}

// 等价转义符将上下文转义符与等效的预定义
// 模板转义符相匹配。
var equivEscapers = map[string]string{
	// 以下几对HTML转义符提供了等效的安全性
	// 保证，因为它们都转义'\000'，'\''，'“'、'&'、'<'和'>'”。
	"_html_template_attrescaper":   "html",
	"_html_template_htmlescaper":   "html",
	"_html_template_rcdataescaper": "html",
	// 这两个URL转义器通过
	// 对RFC 3986节
	// 2.2 
	"_html_template_urlescaper": "urlquery",
	// 中指定的所有保留字符进行百分比编码，生成可安全嵌入URL查询的URL。这两个函数实际上并不等价；urlquery更为严格，因为它
	// 转义保留字符（例如“#”），而html_template_URLnormalizer
	// 不转义保留字符。因此，用urlquery替换_html_template_urlnormalizer 
	// 是安全的（这种情况发生在ensurePipelineContains中），而不是我所使用的其他方法。我们保留此条目是为了保留Go 1.9之前编写的模板
	// 的行为，这可能取决于发生的替换。
	"_html_template_urlnormalizer": "urlquery",
}

// escFnsEq报告两个转义函数是否相等。
func escFnsEq(a, b string) bool {
	return normalizeEscFn(a) == normalizeEscFn(b)
}

// normalizeeescfn（a）等于normalizeeescfn（b），表示
// 转义器函数a和b的任何一对名称相等。
func normalizeEscFn(e string) string {
	if norm := equivEscapers[e]; norm != "" {
		return norm
	}
	return e
}

// redundantFuncs[a][b]表示所有x的funcMap[b]（funcMap[a]（x））==funcMap[a]（x）
// 。
var redundantFuncs = map[string]map[string]bool{
	"_html_template_commentescaper": {
		"_html_template_attrescaper":    true,
		"_html_template_nospaceescaper": true,
		"_html_template_htmlescaper":    true,
	},
	"_html_template_cssescaper": {
		"_html_template_attrescaper": true,
	},
	"_html_template_jsregexpescaper": {
		"_html_template_attrescaper": true,
	},
	"_html_template_jsstrescaper": {
		"_html_template_attrescaper": true,
	},
	"_html_template_urlescaper": {
		"_html_template_urlnormalizer": true,
	},
}

// appendCmd将给定命令追加到命令管道的末尾
// 除非它与最后一个命令冗余。
func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
	if n := len(cmds); n != 0 {
		last, okLast := cmds[n-1].Args[0].(*parse.IdentifierNode)
		next, okNext := cmd.Args[0].(*parse.IdentifierNode)
		if okLast && okNext && redundantFuncs[last.Ident][next.Ident] {
			return cmds
		}
	}
	return append(cmds, cmd)
}

// newIdentCmd生成一个包含单个标识符节点的命令。
func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
	return &parse.CommandNode{
		NodeType: parse.NodeCommand,
		Args:     []parse.Node{parse.NewIdentifier(identifier).SetTree(nil).SetPos(pos)}, // TODO:集合树。
	}
}

// 轻推返回由以下空字符串产生的上下文
// 从输入上下文的转换。例如，解析：
// `<a href=`
// 将在上下文{stateBeforeValue，attrURL}中结束，但解析一个额外的符文：
// `<a href=x`
// 将在上下文{stateURL，delimSpaceOrTagEnd，}中结束。
// 当看到“x”时，会发生两种转换：
// （1）从前值状态转换为起始值状态，而不使用任何字符。
// /（2）使用“x”并转换超过第一个值字符。
// 在本例中，轻推在（1）发生后生成上下文。
func nudge(c context) context {
	switch c.state {
	case stateTag:
		// 在`<foo{.}}}`中，操作应该发出一个属性。
		c.state = stateAttrName
	case stateBeforeValue:
		// 在`<foo bar={{.}}}`中，动作是一个不受限制的值。
		c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
	case stateAfterName:
		// 在`<foo bar{{.}}}`中，操作是一个属性名。
		c.state, c.attr = stateAttrName, attrNone
	}
	return c
}

// join连接分支模板节点的两个上下文。如果任一输入上下文为错误上下文，或者如果
// 错误上下文。
// 输入上下文不同，则结果为
func join(a, b context, node parse.Node, nodeName string) context {
	if a.state == stateError {
		return a
	}
	if b.state == stateError {
		return b
	}
	if a.eq(b) {
		return a
	}

	c := a
	c.urlPart = b.urlPart
	if c.eq(b) {
		// 上下文仅因urlPart而不同。
		c.urlPart = urlPartUnknown
		return c
	}

	c = a
	c.jsCtx = b.jsCtx
	if c.eq(b) {
		// 上下文只因jsCtx不同。
		c.jsCtx = jsCtxUnknown
		return c
	}

	// 允许轻推的上下文与未被轻推的上下文连接。
	// 这意味着
	// <p title={{if.C}}{{end}}{end}}
	// 以无引号的值状态结束，即使else分支
	// 以stateforevalue结束。
	if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
		if e := join(c, d, node, nodeName); e.state != stateError {
			return e
		}
	}

	return context{
		state: stateError,
		err:   errorf(ErrBranchEnd, node, 0, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
	}
}

// escapeBranch转义分支模板节点：“if”、“range”和“with”。
func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
	c0 := e.escapeList(c, n.List)
	if nodeName == "range" && c0.state != stateError {
		// 一个“range”节点的“true”分支可以执行多次。
		// 我们检查执行n.List一次是否会在相同的上下文中产生与执行n.List两次相同的结果。
		c1, _ := e.escapeListConditionally(c0, n.List, nil)
		c0 = join(c0, c1, n, nodeName)
		if c0.state == stateError {
			// 请明确这是循环重入的问题，因为在调试模板时，开发人员往往会忽略该分支。
			c0.err.Line = n.Line
			c0.err.Description = "on range loop re-entry: " + c0.err.Description
			return c0
		}
	}
	c1 := e.escapeList(c, n.ElseList)
	return join(c0, c1, n, nodeName)
}

// escapeList转义列表模板节点。
func (e *escaper) escapeList(c context, n *parse.ListNode) context {
	if n == nil {
		return c
	}
	for _, m := range n.Nodes {
		c = e.escape(c, m)
	}
	return c
}

// EscapeList有条件地转义列表节点，但仅在推理和输出上下文满足筛选条件时，在e中保留编辑和
// 推理。
// 返回对输出上下文的最佳猜测，以及过滤器的结果
// 这与是否更新了e相同。
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
	e1 := makeEscaper(e.ns)
	// 使类型推断可用于f。
	for k, v := range e.output {
		e1.output[k] = v
	}
	c = e1.escapeList(c, n)
	ok := filter != nil && filter(&e1, c)
	if ok {
		// 将e1中的推断和编辑复制回e。
		for k, v := range e1.output {
			e.output[k] = v
		}
		for k, v := range e1.derived {
			e.derived[k] = v
		}
		for k, v := range e1.called {
			e.called[k] = v
		}
		for k, v := range e1.actionNodeEdits {
			e.editActionNode(k, v)
		}
		for k, v := range e1.templateNodeEdits {
			e.editTemplateNode(k, v)
		}
		for k, v := range e1.textNodeEdits {
			e.editTextNode(k, v)
		}
	}
	return c, ok
}

// escapeTemplate转义{{template}}调用节点。
func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
	c, name := e.escapeTree(c, n, n.Name, n.Line)
	if name != n.Name {
		e.editTemplateNode(n, name)
	}
	return c
}

// escapeTree将从给定上下文中开始的命名模板作为
// 必需，并返回其输出上下文。
func (e *escaper) escapeTree(c context, node parse.Node, name string, line int) (context, string) {
	// 将模板名称与输入上下文相混淆，以生成可靠的
	// 标识符。
	dname := c.mangle(name)
	e.called[dname] = true
	if out, ok := e.output[dname]; ok {
		// 已转义。
		return out, dname
	}
	t := e.template(name)
	if t == nil {
		// 两种情况：模板存在但为空，或者在
		// 中从未提及过。区分错误消息中的情况。
		if e.ns.set[name] != nil {
			return context{
				state: stateError,
				err:   errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name),
			}, dname
		}
		return context{
			state: stateError,
			err:   errorf(ErrNoSuchTemplate, node, line, "no such template %q", name),
		}, dname
	}
	if dname != name {
		// 使用先前调用期间派生的任何模板，使用不同的顶级模板对
		// 进行escapeTemplate，必要时进行克隆。
		dt := e.template(dname)
		if dt == nil {
			dt = template.New(dname)
			dt.Tree = &parse.Tree{Name: dname, Root: t.Root.CopyList()}
			e.derived[dname] = dt
		}
		t = dt
	}
	return e.computeOutCtx(c, t), dname
}

// computeOutCtx获取模板及其开始上下文并计算输出
// context，同时将任何推断存储在e中。
func (e *escaper) computeOutCtx(c context, t *template.Template) context {
	// 在正文上传播上下文。
	c1, ok := e.escapeTemplateBody(c, t)
	if !ok {
		// 通过假设c1作为输出上下文来查找固定点。
		if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
			c1, ok = c2, true
		}
		// 如果两种假设都不起作用，则使用c1作为错误上下文。
	}
	if !ok && c1.state != stateError {
		return context{
			state: stateError,
			err:   errorf(ErrOutputContext, t.Tree.Root, 0, "cannot compute output context for template %s", t.Name()),
		}
	}
	return c1
}

// escapeTemplateBody在假定给定输出的情况下，对给定模板进行转义，并返回对输出上下文的最佳猜测以及
// 假设是否正确。
func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
	filter := func(e1 *escaper, c1 context) bool {
		if c1.state == stateError {
			// 不要更新输入转义符，例如。
			return false
		}
		if !e1.called[t.Name()] {
			// 如果不递归调用t，则c1是
			// 精确的输出上下文。
			return true
		}
		// c1如果与我们假设的输出上下文相匹配，则是准确的。
		return c.eq(c1)
	}
	// 我们需要假设一个输出上下文，以便递归模板调用
	// 从escapeTree中获取快速路径，而不是无限递归。
	// 天真地假设输入上下文与输出上下文相同
	// 在90%以上的时间内工作。
	e.output[t.Name()] = c
	return e.escapeListConditionally(c, t.Tree.Root, filter)
}

// delimEnds将每个delim映射到一个字符串，该字符串将终止该delim。
var delimEnds = [...]string{
	delimDoubleQuote: `"`,
	delimSingleQuote: "'",
	// 通过在各种浏览器中运行以下命令，根据经验确定。
	// var div=document.createElement（“div”）；
	// for（var i=0；i<0x10000；++i）{
	// div.innerHTML=“<span title=x”+String.fromCharCode（i）+-bar>”；
	// if（div.getElementsByTagName（“span”）[0]。title.indexOf（“bar”）<0）
	delimSpaceOrTagEnd: " \t\n\f\r>",
}

var doctypeBytes = []byte("<!DOCTYPE")

// escapeText转义文本模板节点。
func (e *escaper) escapeText(c context, n *parse.TextNode) context {
	s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
	for i != len(s) {
		c1, nread := contextAfterText(c, s[i:])
		i1 := i + nread
		if c.state == stateText || c.state == stateRCDATA {
			end := i1
			if c1.state != c.state {
				for j := end - 1; j >= i; j-- {
					if s[j] == '<' {
						end = j
						break
					}
				}
			}
			for j := i; j < end; j++ {
				if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
					b.Write(s[written:j])
					b.WriteString("&lt;")
					written = j + 1
				}
			}
		} else if isComment(c.state) && c.delim == delimNone {
			switch c.state {
			case stateJSBlockCmt:
				// https:
				// “注释的行为类似于空白，被
				// 丢弃，除非多元素
				// 包含行终止符字符，则
				// 整个注释被视为
				// 行终止符，以便
				// 语法分析。“
				if bytes.ContainsAny(s[written:i1], "\n\r\u2028\u2029") {
					b.WriteByte('\n')
				} else {
					b.WriteByte(' ')
				}
			case stateCSSBlockCmt:
				b.WriteByte(' ')
			}
			written = i1
		}
		if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
			// 保留书面和注释开头之间的部分。
			cs := i1 - 2
			if c1.state == stateHTMLCmt {
				// “<！--”而不是“/*”或“
				cs -= 2
			}
			b.Write(s[written:cs])
			written = i1
		}
		if i == i1 && c.state == c1.state {
			panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
		}
		c, i = c1, i1
	}

	if written != 0 && c.state != stateError {
		if !isComment(c.state) || c.delim != delimNone {
			b.Write(n.Text[written:])
		}
		e.editTextNode(n, b.Bytes())
	}
	return c
}

// ”contextAfterext从上下文c开始，使用来自
// s前面的一些标记，然后返回这些标记和未处理的后缀之后的上下文。
func contextAfterText(c context, s []byte) (context, int) {
	if c.delim == delimNone {
		c1, i := tSpecialTagEnd(c, s)
		if i == 0 {
			// 一个特殊的结束标记（`</script>`）已经看到和abcDef
			return c1, 0
		}
		return transitionFunc[c.state](c, s[:i])
	}


	i := bytes.IndexAny(s, delimEnds[c.delim])
	if i == -1 {
		i = len(s)
	}
	if c.delim == delimSpaceOrTagEnd {
		// /”<a class='foo'在值内结束，
		// /”<a style=font:'Arial'“需要打开引号修复。
		// IE将“`”视为引号字符。
		if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
			return context{
				state: stateError,
				err:   errorf(ErrBadHTML, nil, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
			}, len(s)
		}
	}
	if i == len(s) {
		// 保留在属性内。
		// 解码该值，以便非HTML规则可以轻松处理
		// /<button onclick=“alert”（“Hi！”）>
		// 无需实体解码令牌边界。
		for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
			c1, i1 := transitionFunc[c.state](c, u)
			c, u = c1, u[i1:]
		}
		return c, len(s)
	}

	element := c.element

	// 如果这是“script”中的非JS“type”属性标记，不要将内容视为JS.
	if c.state == stateAttr && c.element == elementScript && c.attr == attrScriptType && !isJSType(string(s[:i])) {
		element = elementNone
	}

	if c.delim != delimSpaceOrTagEnd {
		// 使用任何引号。
		i++
	}
	// 在退出属性时，我们将放弃所有状态信息
	// 除了状态和元素。
	return context{state: stateTag, element: element}, i
}

// editActionNode记录对操作管道的更改，以便稍后提交。
func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
	if _, ok := e.actionNodeEdits[n]; ok {
		panic(fmt.Sprintf("node %s shared between templates", n))
	}
	e.actionNodeEdits[n] = cmds
}

// editTemplateNode记录对{{template}的更改稍后提交的被调用方。
func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
	if _, ok := e.templateNodeEdits[n]; ok {
		panic(fmt.Sprintf("node %s shared between templates", n))
	}
	e.templateNodeEdits[n] = callee
}

// editTextNode记录对文本节点的更改以供稍后提交。
func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
	if _, ok := e.textNodeEdits[n]; ok {
		panic(fmt.Sprintf("node %s shared between templates", n))
	}
	e.textNodeEdits[n] = text
}

// commit将更改应用于上下文
// 自动转义内容所需的操作和模板调用，并将任何派生模板添加到集合中。
func (e *escaper) commit() {
	for name := range e.output {
		e.template(name).Funcs(funcMap)
	}
	// ASSOCIAL名称空间中的任何模板带此转义符的ted可用于
	// 将派生模板添加到基础文本/模板名称空间。
	tmpl := e.arbitraryTemplate()
	for _, t := range e.derived {
		if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
			panic("error adding derived template")
		}
	}
	for n, s := range e.actionNodeEdits {
		ensurePipelineContains(n.Pipe, s)
	}
	for n, name := range e.templateNodeEdits {
		n.Name = name
	}
	for n, s := range e.textNodeEdits {
		n.Text = s
	}
	// 重置特定于此提交的状态，以便在后续的提交调用中不会将相同的更改重新应用于模板。
	e.called = make(map[string]bool)
	e.actionNodeEdits = make(map[*parse.ActionNode][]string)
	e.templateNodeEdits = make(map[*parse.TemplateNode]string)
	e.textNodeEdits = make(map[*parse.TextNode][]byte)
}

// template返回指定的模板，给定一个损坏的模板名称。
func (e *escaper) template(name string) *template.Template {
	// 可以使用名称空间中与此转义符关联的任何模板
	// 在基础文本/模板名称空间中查找模板。
	t := e.arbitraryTemplate().text.Lookup(name)
	if t == nil {
		t = e.derived[name]
	}
	return t
}

// arbitraryTemplate从名称空间返回与e关联的任意模板，如果找不到模板，则返回panics。
func (e *escaper) arbitraryTemplate() *Template {
	for _, t := range e.ns.set {
		return t
	}
	panic("no templates in name space")
}

// 转发功能，客户端只需导入此包
// 即可实现文本/模板的一般转义功能。

// HTMLEscape将与纯文本数据b等效的转义HTML写入w。
func HTMLEscape(w io.Writer, b []byte) {
	template.HTMLEscape(w, b)
}

// HTMLEscapeString返回纯文本数据的转义HTML等价物。
func HTMLEscapeString(s string) string {
	return template.HTMLEscapeString(s)
}

// HTMLEscaper返回其参数的文本
// 表示形式的转义HTML等价物。
func HTMLEscaper(args ...interface{}) string {
	return template.HTMLEscaper(args...)
}

// JSEscape向w写入与纯文本数据b等效的转义JavaScript。
func JSEscape(w io.Writer, b []byte) {
	template.JSEscape(w, b)
}

// JSEscapeString返回与纯文本数据等效的转义JavaScript。
func JSEscapeString(s string) string {
	return template.JSEscapeString(s)
}

// JSEscaper返回其参数的文本
// 表示形式的转义JavaScript等价物。
func JSEscaper(args ...interface{}) string {
	return template.JSEscaper(args...)
}

// URLQueryEscaper以适合嵌入URL查询的形式返回其参数的文本表示形式的转义值。
func URLQueryEscaper(args ...interface{}) string {
	return template.URLQueryEscaper(args...)
}
